﻿#region Copyright(C) 2018 FI Ltd. All rights reserved.
// ==================================================================================================
// Copyright(C) 2018 FI Ltd. All rights reserved.
//
// SYSTEM NAME	    : FI.Net Application Framework
// COMPONENT ID	    : FI.Core
// COMPONENT DESC   : FI.Net Core Foundation
//
// CREATED DATE/BY  : 18 Aug 2018 / Tian Mi
//
// REVISION HISTORY :	
// DATE/BY			SR#/CS/PM#/OTHERS		DESCRIPTION OF CHANGE
// ==================================================================================================
#endregion

using FI.Core.Configuration;
using FI.Core.IoC.Configuration;
using FI.Core.Properties;
using System;
using System.Collections.Generic;
using System.Reflection;

namespace FI.Core.IoC
{
    /// <summary>
    /// This is the factory to create or get service locator.
    /// </summary>
    public static class ServiceLocatorFactory
    {
        #region Fields
        private static IServiceLocator serviceLocator;
        private static Dictionary<string, IServiceLocator> serviceLocators;
        private static object syncHelper = new object();
        private static List<Assembly> assemblies;
        private static ReflectedServiceLocatorConfigurator reflectedServiceLocatorConfigurator;
        #endregion

        #region Properties
        /// <summary>
        /// Gets the configurators.
        /// </summary>
        /// <value>
        /// The configurators.
        /// </value>
        public static IList<IServiceLocatorConfigurator> Configurators { get; private set; }
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes the <see cref="ServiceLocatorFactory"/> class.
        /// </summary>
        static ServiceLocatorFactory()
        {
            serviceLocators = new Dictionary<string, IServiceLocator>();
            Configurators = new List<IServiceLocatorConfigurator>();
            assemblies = InitializeResolvedAssemblies();
            reflectedServiceLocatorConfigurator = new ReflectedServiceLocatorConfigurator(new DefaultAssemblyResolver(assemblies));
        }
        
        #endregion

        #region Public Methods
        /// <summary>
        /// Gets the service locator.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <returns>The service locator.</returns>
        public static IServiceLocator GetServiceLocator(string name = null)
        {
            IServiceLocator locator;
            ServiceLocationSettings settings;
            if (string.IsNullOrWhiteSpace(name))
            {
                if (null != serviceLocator)
                {
                    return serviceLocator;
                }

                if (ConfigManager.TryGetConfigurationSection<ServiceLocationSettings>(out settings))
                {
                    locator = settings.GetServiceLocator();
                    if(null!= locator)
                    {
                        serviceLocator = ConfigureServiceLocator(locator);
                        return serviceLocator;
                    }
                }

                locator = reflectedServiceLocatorConfigurator.CreateInstance<IServiceLocator>();
                if (null == locator)
                {
                    throw new InvalidOperationException(Resources.ExceptionDefaultServiceLocatorNotExists);
                }
                return serviceLocator = ConfigureServiceLocator(locator);
            }

            if (serviceLocators.TryGetValue(name, out locator))
            {
                return locator;
            }

            lock (syncHelper)
            {
                if (serviceLocators.TryGetValue(name, out locator))
                {
                    return locator;
                }
                settings = ConfigManager.GetConfigurationSection<ServiceLocationSettings>();
                locator = ConfigureServiceLocator(settings.GetServiceLocator(name));
                serviceLocators[name] = locator;
                return locator;
            }
        }

        /// <summary>
        /// Adds the resovled assemblies.
        /// </summary>
        /// <param name="assemblies">The assemblies.</param>
        public static void AddResolvedAssemblies(params Assembly[] assemblies)
        {
            Reset();
            assemblies = assemblies ?? new Assembly[0];
            foreach (Assembly assembly in assemblies)
            {
                if (!ServiceLocatorFactory.assemblies.Contains(assembly))
                {
                    ServiceLocatorFactory.assemblies.Add(assembly);
                }
            }
        }

        /// <summary>
        /// Clear all cached service locators.
        /// </summary>
        public static void Reset()
        {
            lock (syncHelper)
            {
                serviceLocator = null;
                serviceLocators.Clear();
                Configurators.Clear();
                assemblies = InitializeResolvedAssemblies();
                reflectedServiceLocatorConfigurator = new ReflectedServiceLocatorConfigurator(new DefaultAssemblyResolver(assemblies));
            }
        }
        #endregion 

        #region Private Methods
        internal static IServiceLocator ConfigureServiceLocator(IServiceLocator serviceLocator)
        {
            //Use custom ServiceLocatorConfigurator to configure service locator.
            foreach (IServiceLocatorConfigurator configurator in Configurators)
            {
                configurator.Configure(serviceLocator);
            }
            
            //Use configuration to configure service locator.
            foreach (string sectionName in Settings.SectionNames)
            {
                ServiceLocatableSettings settings;
                if (ConfigManager.TryGetConfigurationSection<ServiceLocatableSettings>(sectionName, out settings))
                {
                    settings.Configure(serviceLocator);
                }           
            }
            reflectedServiceLocatorConfigurator.Configure(serviceLocator);
            return serviceLocator;
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        private static bool TryParseAssemblyName(string displayName, out AssemblyName assemblyName)
        {
            assemblyName = null;
            try
            {
                assemblyName = new AssemblyName(displayName);
                return null != assemblyName;
            }
            catch
            {
                return false;
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Reflection.Assembly.LoadFile")]
        private static List<Assembly> InitializeResolvedAssemblies()
        {
            List<Assembly> assemblies = new List<Assembly>();
            //Add default assemblies;
            foreach (Assembly assembly in Assemblies.GetAssemblies())
            {
                assemblies.Add(assembly);
            }
            //Add configured assemblies.
            ServiceLocationSettings settings;
            if (ConfigManager.TryGetConfigurationSection<ServiceLocationSettings>(out settings) && null != settings.ResolvedAssemblies)
            {
                foreach (AssemblyConfigurationElement element in settings.ResolvedAssemblies)
                {
                    AssemblyName assemblyName;
                    if (TryParseAssemblyName(element.Assembly, out assemblyName))
                    {
                        Assembly assembly = Assembly.Load(assemblyName);
                        if (null != assembly && !assemblies.Contains(assembly))
                        {
                            assemblies.Add(assembly);
                        }
                    }
                    else
                    {
                        Assembly assembly = Assembly.LoadFile(element.Assembly);
                        if (null != assembly && !assemblies.Contains(assembly))
                        {
                            assemblies.Add(assembly);
                        }
                    }
                }
            }
            return assemblies;
        }
        #endregion
    }
}
